home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-07-03 | 55.9 KB | 1,652 lines | [TEXT/MPS ] |
- (*------------------------------------------------------------------------------
- #
- # Apple Macintosh Developer Technical Support
- #
- # MultiFinder-Aware Simple TextEdit Sample Application
- #
- # TESample
- #
- # TESample.p - Pascal Source
- #
- # Copyright © 1989 Apple Computer, Inc.
- # All rights reserved.
- #
- # Versions:
- # 1.00 08/88
- # 1.01 11/88
- # 1.02 04/89
- # 1.03 06/89
- #
- # Components:
- # TESample.p Feb. 1, 1990
- # TESampleGlue.a Feb. 1, 1990
- # TESample.r Feb. 1, 1990
- # TESample.h Feb. 1, 1990
- # TESample.make Feb. 1, 1990
- #
- # TESample is an example application that demonstrates how
- # to initialize the commonly used toolbox managers, operate
- # successfully under MultiFinder, handle desk accessories and
- # create, grow, and zoom windows. The fundamental TextEdit
- # toolbox calls and TextEdit autoscroll are demonstrated. It
- # also shows how to create and maintain scrollbar controls.
- #
- # It does not by any means demonstrate all the techniques you
- # need for a large application. In particular, Sample does not
- # cover exception handling, multiple windows/documents,
- # sophisticated memory management, printing, or undo. All of
- # these are vital parts of a normal full-sized application.
- #
- # This application is an example of the form of a Macintosh
- # application; it is NOT a template. It is NOT intended to be
- # used as a foundation for the next world-class, best-selling,
- # 600K application. A stick figure drawing of the human body may
- # be a good example of the form for a painting, but that does not
- # mean it should be used as the basis for the next Mona Lisa.
- #
- # We recommend that you review this program or Sample before
- # beginning a new application. Sample is a simple app. which doesn’t
- # use TextEdit or the Control Manager.
- #
- ------------------------------------------------------------------------------*)
-
-
- MODULE TESample;
-
-
- (*Segmentation strategy:
-
- This program consists of three segments. Main contains most of the code,
- including the MPW libraries, and the main program. Initialize contains
- code that is only used once, during startup, and can be unloaded after the
- program starts. %A5Init is automatically created by the Linker to initialize
- globals for the MPW libraries and is unloaded right away.*)
-
-
- (*SetPort strategy:
-
- Toolbox routines do not change the current port. In spite of this, in this
- program we use a strategy of calling SetPort whenever we want to draw or
- make calls which depend on the current port. This makes us less vulnerable
- to bugs in other software which might alter the current port (such as the
- bug (feature?) in many desk accessories which change the port on OpenDeskAcc).
- Hopefully, this also makes the routines from this program more self-contained,
- since they don't depend on the current port setting.*)
-
-
- (*Clipboard strategy:
-
- This program does not maintain a private scrap. Whenever a cut, copy, or paste
- occurs, we import/export from the public scrap to TextEdit's scrap right away,
- using the TEToScrap and TEFromScrap routines. If we did use a private scrap,
- the import/export would be in the activate/deactivate event and suspend/resume
- event routines.*)
-
- IMPORT SYSTEM, Types, Traps, Memory, Quickdraw, Windows, Events, TextEdit,
- Controls, Dialogs, Fonts, Menus, OSUtils, ToolUtils, SegLoad,
- Scrap, Desk, DiskInit, TextUtils;
-
-
- CONST
- (*MPW 3.0 will include a Traps.p interface file that includes constants for trap numbers.
- These constants are from that file.*)
- (*1.02 - Uses Traps.p to obtain trap numbers.*)
-
- (*1.01 - changed constants to begin with 'k' for consistency, except for resource IDs*)
- (*kTextMargin is the number of pixels we leave blank at the edge of the window.*)
- kTextMargin = 2;
-
- (*kMaxOpenDocuments is used to determine whether a new document can be opened
- or created. We keep track of the number of open documents, and disable the
- menu items that create a new document when the maximum is reached. If the
- number of documents falls below the maximum, the items are enabled again.*)
- kMaxOpenDocuments = 1;
-
- (*kMaxDocWidth is an arbitrary number used to specify the width of the TERec's
- destination rectangle so that word wrap and horizontal scrolling can be
- demonstrated.*)
- kMaxDocWidth = 576;
-
- (*kMinDocDim is used to limit the minimum dimension of a window when GrowWindow
- is called.*)
- kMinDocDim = 64;
-
- (*kControlInvisible is used to 'turn off' controls (i.e., cause the control not
- to be redrawn as a result of some Control Manager call such as SetCtlValue)
- by being put into the contrlVis field of the record. kControlVisible is used
- the same way to 'turn on' the control.*)
- kControlInvisible = 0;
- kControlVisible = $FF;
-
- (*kScrollBarAdjust and kScrollBarWidth are used in calculating
- values for control positioning and sizing.*)
- kScrollbarWidth = 16;
- kScrollbarAdjust = kScrollbarWidth - 1;
-
- (*kScrollTweek compensates for off-by-one requirements of the scrollbars
- to have borders coincide with the growbox.*)
- kScrollTweek = 2;
-
- (*kCrChar is used to match with a carriage return when calculating the
- number of lines in the TextEdit record. kDelChar is used to check for
- delete in keyDowns.*)
- kCRChar = 0DX;
- kDelChar = 08X;
-
- (*kButtonScroll is how many pixels to scroll horizontally when the button part
- of the horizontal scrollbar is pressed.*)
- kButtonScroll = 4;
-
- (*kMaxTELength is an arbitrary number used to limit the length of text in the TERec
- so that various errors won't occur from too many characters in the text.*)
- kMaxTELength = 32000;
-
- (*kSysEnvironsVersion is passed to SysEnvirons to tell it which version of the
- SysEnvRec we understand.*)
- kSysEnvironsVersion = 1;
-
- (*kOSEvent is the event number of the suspend/resume and mouse-moved events sent
- by MultiFinder. Once we determine that an event is an osEvent, we look at the
- high byte of the message sent to determine which kind it is. To differentiate
- suspend and resume events we check the resumeMask bit.*)
- kOSEvent = Events.app4Evt; (*event used by MultiFinder*)
- kSuspendResumeMessage = 1; (*high byte of suspend/resume event message*)
- kResumeMask = 1; (*bit of message field for resume vs. suspend*)
- kMouseMovedMessage = $FA; (*high byte of mouse-moved event message*)
- kNoEvents = 0; (*no events mask*)
-
- (*1.01 - kMinHeap - This is the minimum result from the following
- equation:
-
- ORD(GetApplLimit) - ORD(ApplicZone)
-
- for the application to run. It will insure that enough memory will
- be around for reasonable-sized scraps, FKEYs, etc. to exist with the
- application, and still give the application some 'breathing room'.
- To derive this number, we ran under a MultiFinder partition that was
- our requested minimum size, as given in the 'SIZE' resource.*)
-
- kMinHeap = 29 * 1024;
-
- (*1.01 - kMinSpace - This is the minimum result from PurgeSpace, when called
- at initialization time, for the application to run. This number acts
- as a double-check to insure that there really is enough memory for the
- application to run, including what has been taken up already by
- pre-loaded resources, the scrap, code, and other sundry memory blocks.*)
-
- kMinSpace = 20 * 1024;
-
- (*kExtremeNeg and kExtremePos are used to set up wide open rectangles and regions.*)
- kExtremeNeg = -32768;
- kExtremePos = 32767 - 1; (*required for old region bug*)
-
- (*kTESlop provides some extra security when pre-flighting edit commands.*)
- kTESlop = 1024;
-
- (*kErrStrings is the resource ID for the error strings STR# resource.*)
- kErrStrings = 128;
-
- (*The following are indicies into STR# resources.*)
- eWrongMachine = 1;
- eSmallSize = 2;
- eNoMemory = 3;
- eNoSpaceCut = 4;
- eNoCut = 5;
- eNoCopy = 6;
- eExceedPaste = 7;
- eNoSpacePaste = 8;
- eNoWindow = 9;
- eExceedChar = 10;
- eNoPaste = 11;
-
- (*The following constants are all resource IDs, corresponding to resources in Sample.r.*)
- rMenuBar = 128; (*application's menu bar*)
- rAboutAlert = 128; (*about alert*)
- rUserAlert = 129; (*user error alert*)
- rDocWindow = 128; (*application's window*)
- rVScroll = 128; (*vertical scrollbar control*)
- rHScroll = 129; (*horizontal scrollbar control*)
-
- (*The following constants are used to identify menus and their items. The menu IDs
- have an "m" prefix and the item numbers within each menu have an "i" prefix.*)
- mApple = 128; (*Apple menu*)
- iAbout = 1;
-
- mFile = 129; (*File menu*)
- iNew = 1;
- iClose = 4;
- iQuit = 12;
-
- mEdit = 130; (*Edit menu*)
- iUndo = 1;
- iCut = 3;
- iCopy = 4;
- iPaste = 5;
- iClear = 6;
-
- (*1.01 - kDITop and kDILeft are used to locate the Disk Initialization dialogs.*)
- kDITop = $0050;
- kDILeft = $0070;
-
-
- TYPE
- (*A DocumentRecord contains the WindowRecord for one of our document windows,
- as well as the TEHandle for the text we are editing. We have added fields to
- hold the ControlHandles to the vertical and horizontal scrollbars and to hold
- the address of the default clikLoop that gets attached to a TERec when you call
- TEAutoView. Other document fields can be added to this record as needed. For
- a similar example, see how the Window Manager and Dialog Manager add fields
- after the GrafPort.*)
- DocumentRecord = RECORD(Windows.WindowRecord)
- (*docWindow : Windows.WindowRecord;*)
- docTE : TextEdit.TEHandle;
- docVScroll : Controls.ControlHandle;
- docHScroll : Controls.ControlHandle;
- docClik : TextEdit.ClikLoopProcPtr;
- END;
- DocumentPeek = POINTER TO DocumentRecord;
-
-
- VAR
- (*The "g" prefix is used to emphasize that a variable is global.*)
-
- (*GMac is used to hold the result of a SysEnvirons call. This makes
- it convenient for any routine to check the environment. It is
- global information, anyway.*)
- gMac : OSUtils.SysEnvRec; (*set up by Initialize*)
-
- (*GHasWaitNextEvent is set at startup, and tells whether the WaitNextEvent
- trap is available. If it is false, we know that we must call GetNextEvent.*)
- gHasWaitNextEvent : BOOLEAN; (*set up by Initialize*)
-
- (*GInBackground is maintained by our OSEvent handling routines. Any part of
- the program can check it to find out if it is currently in the background.*)
- gInBackground : BOOLEAN; (*maintained by Initialize and DoEvent*)
-
- (*GNumDocuments is used to keep track of how many open documents there are
- at any time. It is maintained by the routines that open and close documents.*)
- gNumDocuments : INTEGER; (*maintained by Initialize, DoNew, and DoCloseWindow*)
-
-
- (*$S Initialize*)
- PROCEDURE TrapAvailable(tNumber: INTEGER; tType: OSUtils.TrapType): BOOLEAN;
-
- (*Check to see if a given trap is implemented. This is only used by the
- Initialize routine in this program, so we put it in the Initialize segment.
- The recommended approach to see if a trap is implemented is to see if
- the address of the trap routine is the same as the address of the
- Unimplemented trap.*)
- (*1.02 - Needs to be called after call to SysEnvirons so that it can check
- if a ToolTrap is out of range of a pre-MacII ROM.*)
-
- BEGIN
- IF (tType = OSUtils.ToolTrap) &
- (gMac.machineType > OSUtils.envMachUnknown) &
- (gMac.machineType < OSUtils.envMacII) THEN (*it's a 512KE, Plus, or SE*)
- tNumber := BAND(tNumber, $03FF);
- IF tNumber > $01FF THEN (*which means the tool traps*)
- tNumber := Traps._Unimplemented (*only go to $01FF*)
- END
- END;
- RETURN OSUtils.NGetTrapAddress(tNumber, tType) #
- OSUtils.GetTrapAddress(Traps._Unimplemented);
- END TrapAvailable;
-
-
- (*$S Main*)
- PROCEDURE IsDAWindow(window: Windows.WindowPtr): BOOLEAN;
-
- (*Check if a window belongs to a desk accessory.*)
-
- BEGIN
- IF window = NIL THEN
- RETURN FALSE
- ELSE (*DA windows have negative windowKinds*)
- RETURN window.windowKind < 0
- END
- END IsDAWindow;
-
-
- (*$S Main*)
- PROCEDURE IsAppWindow(window: Windows.WindowPtr): BOOLEAN;
-
- (*Check to see if a window belongs to the application. If the window pointer
- passed was NIL, then it could not be an application window. WindowKinds
- that are negative belong to the system and windowKinds less than userKind
- are reserved by Apple except for windowKinds equal to dialogKind, which
- mean it is a dialog.
- 1.02 - In order to reduce the chance of accidentally treating some window
- as an AppWindow that shouldn't be, we'll only return true if the windowkind
- is userKind. If you add different kinds of windows to Sample you'll need
- to change how this all works.*)
-
- BEGIN
- IF window = NIL THEN
- RETURN FALSE
- ELSE (*application windows have windowKinds = userKind (8)*)
- RETURN window.windowKind = Windows.userKind
- END
- END IsAppWindow;
-
-
- (*$S Main*)
- PROCEDURE AlertUser(error: INTEGER);
-
- (*Display an alert that tells the user an error occurred, then exit the program.
- This routine is used as an ultimate bail-out for serious errors that prohibit
- the continuation of the application. Errors that do not require the termination
- of the application should be handled in a different manner. Error checking and
- reporting has a place even in the simplest application. The error number is used
- to index an 'STR#' resource so that a relevant message can be displayed.*)
-
- VAR
- itemHit : INTEGER;
- message : Types.Str255;
- BEGIN
- Quickdraw.SetCursor(Quickdraw.arrow);
- TextUtils.GetIndString(message, kErrStrings, error);
- Dialogs.ParamText(message, "", "", "");
- itemHit := Dialogs.Alert(rUserAlert, NIL);
- END AlertUser;
-
-
- (*$S Main*)
- PROCEDURE GetTERect(window: Windows.WindowPtr; VAR teRect: Types.Rect);
-
- (*return a rectangle that is inset from the portRect by the size of
- the scrollbars and a little extra margin.*)
-
- BEGIN
- (*$TYPECHECK- type checks off*)
- teRect := window.portRect;
- (*$TYPECHECK+ type checks on*)
- Quickdraw.InsetRect(teRect, kTextMargin, kTextMargin); (*adjust for margin*)
- DEC(teRect.botRight.v, kScrollbarAdjust); (*and for the scrollbars*)
- DEC(teRect.botRight.h, kScrollbarAdjust)
- END GetTERect;
-
-
- (*$S Main*)
- PROCEDURE DoCloseWindow(window: Windows.WindowPtr) : BOOLEAN;
-
- (*Close a window. This handles desk accessory and application windows.*)
-
- (*1.01 - At this point, if there was a document associated with a
- window, you could do any document saving processing if it is 'dirty'.
- DoCloseWindow would return TRUE if the window actually closed, i.e.,
- the user didn’t cancel from a save dialog. This result is handy when
- the user quits an application, but then cancels the save of a document
- associated with a window.*)
-
- BEGIN
- IF IsDAWindow(window) THEN
- Desk.CloseDeskAcc(window.windowKind)
- ELSIF IsAppWindow(window) THEN
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- IF window.docTE # NIL THEN
- TextEdit.TEDispose(window.docTE); (*dispose the TEHandle*)
- END
- END;
- (*1.01 - We used to call DisposeWindow, but that was technically
- incorrect, even though we allocated storage for the window on
- the heap. We should instead call CloseWindow to have the structures
- taken care of and then dispose of the storage ourselves.*)
- Windows.CloseWindow(window);
- Memory.DisposPtr(Types.Ptr(window));
- DEC(gNumDocuments)
- END;
- RETURN TRUE
- END DoCloseWindow;
-
-
- (*$S Main*)
- PROCEDURE AdjustViewRect(docTE: TextEdit.TEHandle);
-
- (*Update the TERec's view rect so that it is the greatest multiple of
- the lineHeight and still fits in the old viewRect.*)
-
- BEGIN
- docTE.viewRect.botRight.v := (((docTE.viewRect.botRight.v - docTE.viewRect.topLeft.v) DIV docTE.lineHeight) *
- docTE.lineHeight) + docTE.viewRect.topLeft.v;
- END AdjustViewRect;
-
-
- (*$S Main*)
- PROCEDURE AdjustTE(window: Windows.WindowPtr);
-
- (*Scroll the TERec around to match up to the potentially updated scrollbar
- values. This is really useful when the window resizes such that the
- scrollbars become inactive and the TERec had been previously scrolled.*)
-
- VAR
- te : TextEdit.TEHandle;
- BEGIN
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- te:=window.docTE;
- TextEdit.TEScroll(
- (te.viewRect.topLeft.h - te.destRect.topLeft.h)
- - Controls.GetCtlValue(window.docHScroll),
- (te.viewRect.topLeft.v - te.destRect.topLeft.v)
- - (Controls.GetCtlValue(window.docVScroll) * te.lineHeight),
- te)
- END
- END AdjustTE;
-
-
- (*$S Main*)
- PROCEDURE AdjustHV(isVert: BOOLEAN; control: Controls.ControlHandle; docTE: TextEdit.TEHandle; canRedraw: BOOLEAN);
-
- (*Calculate the new control maximum value and current value, whether it is the horizontal or
- vertical scrollbar. The vertical max is calculated by comparing the number of lines to the
- vertical size of the viewRect. The horizontal max is calculated by comparing the maximum document
- width to the width of the viewRect. The current values are set by comparing the offset between
- the view and destination rects. If necessary and we canRedraw, have the control be re-drawn by
- calling ShowControl.*)
-
- VAR
- value, lines, max : INTEGER;
- oldValue, oldMax : INTEGER;
- ch: CHAR;
- BEGIN
- oldValue := Controls.GetCtlValue(control);
- oldMax := Controls.GetCtlMax(control);
- IF isVert THEN
- lines := docTE.nLines;
- (*since nLines isn’t right if the last character is a return, check for that case*)
- SYSTEM.GET(LONGINT(MASTERPTR(docTE.hText))+docTE.teLength-1, ch);
- IF ch = kCRChar THEN
- lines := lines + 1
- END;
- max := lines - ((docTE.viewRect.botRight.v - docTE.viewRect.topLeft.v) DIV docTE.lineHeight);
- ELSE
- max := kMaxDocWidth - (docTE.viewRect.botRight.h - docTE.viewRect.topLeft.h);
- END;
- IF max < 0 THEN max := 0 END; (*check for negative values*)
- Controls.SetCtlMax(control, max);
- IF isVert THEN
- value := (docTE.viewRect.topLeft.v - docTE.destRect.topLeft.v) DIV docTE.lineHeight
- ELSE
- value := docTE.viewRect.topLeft.h - docTE.destRect.topLeft.h
- END;
- IF value < 0 THEN
- value := 0
- ELSIF value > max THEN
- value := max (*pin the value to within range*)
- END;
- Controls.SetCtlValue(control, value);
- IF canRedraw & ( (max # oldMax) OR (value # oldValue) ) THEN
- Controls.ShowControl(control); (*check to see if the control can be re-drawn*)
- END
- END AdjustHV;
-
-
- (*$S Main*)
- PROCEDURE AdjustScrollValues(window: Windows.WindowPtr; canRedraw: BOOLEAN);
-
- (*Simply call the common adjust routine for the vertical and horizontal scrollbars.*)
-
- BEGIN
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- AdjustHV(TRUE, window.docVScroll, window.docTE, canRedraw);
- AdjustHV(FALSE, window.docHScroll, window.docTE, canRedraw);
- END
- END AdjustScrollValues;
-
-
- (*$S Main*)
- PROCEDURE AdjustScrollSizes(window: Windows.WindowPtr);
-
- (*Re-calculate the position and size of the viewRect and the scrollbars.
- kScrollTweek compensates for off-by-one requirements of the scrollbars
- to have borders coincide with the growbox.*)
-
- VAR
- teRect : Types.Rect;
- BEGIN
- GetTERect(window, teRect); (*start with teRect*)
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- window.docTE.viewRect := teRect;
- AdjustViewRect(window.docTE); (*snap to nearest line*)
- Controls.MoveControl(window.docVScroll, window.portRect.botRight.h - kScrollbarAdjust, -1);
- Controls.SizeControl(window.docVScroll, kScrollbarWidth, (window.portRect.botRight.v - window.portRect.botRight.v) -
- (kScrollbarAdjust - kScrollTweek));
- Controls.MoveControl(window.docHScroll, -1, window.portRect.botRight.v - kScrollbarAdjust);
- Controls.SizeControl(window.docHScroll, (window.portRect.botRight.h - window.portRect.topLeft.h) - (kScrollbarAdjust -
- kScrollTweek), kScrollbarWidth);
- END
- END AdjustScrollSizes;
-
-
- (*$S Main*)
- PROCEDURE AdjustScrollbars(window: Windows.WindowPtr; needsResize: BOOLEAN);
-
- (*Turn off the controls by jamming a zero into their contrlVis fields (HideControl erases them
- and we don't want that). If the controls are to be resized as well, call the procedure to do that,
- then call the procedure to adjust the maximum and current values. Finally re-enable the controls
- by jamming a $FF in their contrlVis fields.*)
-
- BEGIN
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- (*First, turn visibility of scrollbars off so we won’t get unwanted redrawing*)
- window.docVScroll.contrlVis := kControlInvisible; (*turn them off*)
- window.docHScroll.contrlVis := kControlInvisible;
- IF needsResize THEN (*move and size if needed*)
- AdjustScrollSizes(window)
- END;
- AdjustScrollValues(window, ¬needsResize); (*fool with max and current value*)
- (*Now, restore visibility in case we never had to ShowControl during adjustment*)
- window.docVScroll.contrlVis := kControlVisible; (*turn them on*)
- window.docHScroll.contrlVis := kControlVisible;
- END;
- END AdjustScrollbars;
-
-
- (*$S Main*)
- (*$Calling Pascal*)
- PROCEDURE PascalClikLoop*;
-
- (*Gets called from our assembly language routine, AsmClikLoop, which is in
- turn called by the TEClick toolbox routine. Saves the windows clip region,
- sets it to the portRect, adjusts the scrollbar values to match the TE scroll
- amount, then restores the clip region.*)
-
- VAR
- window : Windows.WindowPtr;
- region : Quickdraw.RgnHandle;
- BEGIN
- window := Windows.FrontWindow();
- region := Quickdraw.NewRgn();
- Quickdraw.GetClip(region); (*save the old clip*)
- Quickdraw.ClipRect(window.portRect); (*set the new clip*)
- AdjustScrollValues(window, TRUE); (*pass TRUE for canRedraw*)
- Quickdraw.SetClip(region); (*restore the old clip*)
- Quickdraw.DisposeRgn(region);
- END PascalClikLoop;
-
-
- (*$S Main*)
- PROCEDURE GetOldClikLoop*(): TextEdit.ClikLoopProcPtr;
-
- (*Gets called from our assembly language routine, AsmClikLoop, which is in
- turn called by the TEClick toolbox routine. It returns the address of the
- default clikLoop routine that was put into the TERec by TEAutoView to
- AsmClikLoop so that it can call it.*)
-
- VAR
- window : Windows.WindowPtr;
- BEGIN
- window:=Windows.FrontWindow();
- (*$TYPECHECK- type checks off*)
- RETURN window(DocumentPeek).docClik
- (*$TYPECHECK+ type checks on*)
- END GetOldClikLoop;
- (*$Calling Oberon*)
-
-
- PROCEDURE AsmClikLoop(): BOOLEAN;
- EXTERNAL PASCAL;
-
- (*A reference to our assembly language routine that gets attached to the clikLoop
- field of our TE record.*)
-
-
- (*$S Main*)
- PROCEDURE DoNew;
-
- (*Create a new document and window.*)
-
- VAR
- good, ignore : BOOLEAN;
- storage : Types.Ptr;
- window : Windows.WindowPtr;
- destRect, viewRect : Types.Rect;
-
- BEGIN
- storage := Memory.NewPtr(SIZE(DocumentRecord));
- IF storage # NIL THEN
- window := Windows.GetNewWindow(rDocWindow, storage, Windows.WindowPtr(-1));
- IF window # NIL THEN
- gNumDocuments := gNumDocuments + 1; (*this will be decremented when we call DoCloseWindow*)
- good := FALSE;
- Quickdraw.SetPort(window);
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- GetTERect(window, viewRect); destRect := viewRect;
- destRect.botRight.h := destRect.topLeft.h + kMaxDocWidth;
- window.docTE := TextEdit.TENew(destRect, viewRect);
- IF window.docTE # NIL THEN (*1.02 - moved*)
- good := TRUE; (*if TENew succeeded, we have a good document*)
- AdjustViewRect(window.docTE);
- TextEdit.TEAutoView(TRUE, window.docTE);
- window.docClik := window.docTE.clikLoop;
- window.docTE.clikLoop := AsmClikLoop;
- END;
- IF good THEN
- window.docVScroll := Controls.GetNewControl(rVScroll, window);
- good := window.docVScroll # NIL;
- END;
- IF good THEN
- window.docHScroll := Controls.GetNewControl(rHScroll, window);
- good := window.docHScroll # NIL;
- END;
- IF good THEN
- (*FALSE to AdjustScrollValues means musn’t redraw; technically, of course,
- the window is hidden so it wouldn’t matter whether we called ShowControl or not.*)
- AdjustScrollValues(window, FALSE);
- Windows.ShowWindow(window); (*if the document is good, make the window visible*)
- ELSE
- ignore := DoCloseWindow(window); (*otherwise regret we ever created it...*)
- AlertUser(eNoWindow); (*and tell user*)
- END
- END
- ELSE
- Memory.DisposPtr(storage) (*get rid of the storage if it is never used*)
- END
- END;
- END DoNew;
-
-
- (*$S Main*)
- PROCEDURE BigBadError(error: INTEGER);
- BEGIN
- AlertUser(error);
- SegLoad.ExitToShell;
- END BigBadError;
-
-
- (*$S Initialize*)
- PROCEDURE Initialize;
-
- (*Set up the whole world, including global variables, Toolbox managers,
- menus, and a single blank document.*)
-
- (*1.01 - The code that used to be part of ForceEnvirons has been moved into
- this module. If an error is detected, instead of merely doing an ExitToShell,
- which leaves the user without much to go on, we call AlertUser, which puts
- up a simple alert that just says an error occurred and then calls ExitToShell.
- Since there is no other cleanup needed at this point if an error is detected,
- this form of error- handling is acceptable. If more sophisticated error recovery
- is needed, an exception mechanism, such as is provided by Signals, can be used.*)
-
- VAR
- menuBar : Types.Handle;
- total, contig : LONGINT;
- ignoreResult : BOOLEAN;
- event : Events.EventRecord;
- count, ignoreError : INTEGER;
-
- BEGIN
- gInBackground := FALSE;
-
- Quickdraw.InitGraf(Quickdraw.thePort);
- Fonts.InitFonts;
- Windows.InitWindows;
- Menus.InitMenus;
- TextEdit.TEInit;
- Dialogs.InitDialogs(NIL);
- Quickdraw.InitCursor;
-
- (*Call MPPOpen and ATPLoad at this point to initialize AppleTalk,
- if you are using it.*)
- (*NOTE -- It is no longer necessary, and actually unhealthy, to check
- PortBUse and SPConfig before opening AppleTalk. The drivers are capable
- of checking for port availability themselves.*)
-
- (*This next bit of code is necessary to allow the default button of our
- alert be outlined.
- 1.02 - Changed to call EventAvail so that we don't lose some important
- events.*)
-
- FOR count := 1 TO 3 DO
- ignoreResult := Events.EventAvail(Events.everyEvent, event)
- END;
-
- (*Ignore the error returned from SysEnvirons; even if an error occurred,
- the SysEnvirons glue will fill in the SysEnvRec. You can save a redundant
- call to SysEnvirons by calling it after initializing AppleTalk.*)
-
- ignoreError := OSUtils.SysEnvirons(kSysEnvironsVersion, gMac);
-
- (*Make sure that the machine has at least 128K ROMs. If it doesn't, exit.*)
-
- IF gMac.machineType < 0 THEN BigBadError(eWrongMachine) END;
-
- (*1.02 - Move TrapAvailable call to after SysEnvirons so that we can tell
- in TrapAvailable if a tool trap value is out of range.*)
-
- gHasWaitNextEvent := TrapAvailable(Traps._WaitNextEvent, OSUtils.ToolTrap);
-
- (*1.01 - We used to make a check for memory at this point by examining ApplLimit,
- ApplicZone, and StackSpace and comparing that to the minimum size we told
- MultiFinder we needed. This did not work well because it assumed too much about
- the relationship between what we asked MultiFinder for and what we would actually
- get back, as well as how to measure it. Instead, we will use an alternate
- method comprised of two steps.*)
-
- (*It is better to first check the size of the application heap against a value
- that you have determined is the smallest heap the application can reasonably
- work in. This number should be derived by examining the size of the heap that
- is actually provided by MultiFinder when the minimum size requested is used.
- The derivation of the minimum size requested from MultiFinder is described
- in Sample.h. The check should be made because the preferred size can end up
- being set smaller than the minimum size by the user. This extra check acts to
- insure that your application is starting from a solid memory foundation.*)
-
- IF LONGINT(Memory.GetApplLimit()) - LONGINT(Memory.ApplicZone()) < kMinHeap THEN BigBadError(eSmallSize) END;
-
- (*Next, make sure that enough memory is free for your application to run. It
- is possible for a situation to arise where the heap may have been of required
- size, but a large scrap was loaded which left too little memory. To check for
- this, call PurgeSpace and compare the result with a value that you have determined
- is the minimum amount of free memory your application needs at initialization.
- This number can be derived several different ways. One way that is fairly
- straightforward is to run the application in the minimum size configuration
- as described previously. Call PurgeSpace at initialization and examine the value
- returned. However, you should make sure that this result is not being modified
- by the scrap's presence. You can do that by calling ZeroScrap before calling
- PurgeSpace. Make sure to remove that call before shipping, though.*)
-
- (** ZeroScrap; **)
- Memory.PurgeSpace(total, contig);
- IF total < kMinSpace THEN
- IF Scrap.UnloadScrap() # Types.noErr THEN
- BigBadError(eNoMemory)
- ELSE
- Memory.PurgeSpace(total, contig);
- IF total < kMinSpace THEN
- BigBadError(eNoMemory)
- END
- END
- END;
-
- (*The extra benefit to waiting until after the Toolbox Managers have been initialized
- to check memory is that we can now give the user an alert to tell her/him what
- happened. Although it is possible that the memory situation could be worsened by
- displaying an alert, MultiFinder would gracefully exit the application with
- an informative alert if memory became critical. Here we are acting more
- in a preventative manner to avoid future disaster from low-memory problems.*)
-
- menuBar := Menus.GetNewMBar(rMenuBar); (*read menus into menu bar*)
- IF menuBar = NIL THEN
- BigBadError(eNoMemory)
- END;
- Menus.SetMenuBar(menuBar); (*install menus*)
- Memory.DisposHandle(menuBar);
- Menus.AddResMenu(Menus.GetMHandle(mApple), LONG("DRVR")); (*add DA names to Apple menu*)
- Menus.DrawMenuBar;
-
- gNumDocuments := 0;
-
- (*do other initialization here*)
-
- DoNew; (*create a single empty document*)
- END Initialize;
-
-
- (**************************************************************************************
- 1.01 - PROCEDURE DoCloseBehind(window: WindowPtr); was removed.
-
- (*1.01 - DoCloseBehind was a good idea for closing windows when quitting
- and not having to worry about updating the windows, but it suffered
- from a fatal flaw. If a desk accessory owned two windows, it would
- close both those windows when CloseDeskAcc was called. When DoCloseBehind
- got around to calling DoCloseWindow for that other window that was already
- closed, things would go very poorly. Another option would be to have a
- procedure, GetRearWindow, that would go through the window list and return
- the last window. Instead, we decided to present the standard approach
- of getting and closing FrontWindow until FrontWindow returns NIL. This
- has a potential benefit in that the window whose document needs to be saved
- may be visible since it is the front window, therefore decreasing the
- chance of user confusion. For aesthetic reasons, the windows in the
- application should be checked for updates periodically and have the
- updates serviced.*)
- **************************************************************************************)
-
-
- (*$S Main*)
- PROCEDURE Terminate;
-
- (*Clean up the application and exit. We close all of the windows so that
- they can update their documents, if any.*)
-
- (*1.01 - If we find out that a cancel has occurred, we won't exit to the
- shell, but will return instead.*)
-
- VAR
- aWindow : Windows.WindowPtr;
- closed : BOOLEAN;
-
- BEGIN
- closed := TRUE;
- REPEAT
- aWindow := Windows.FrontWindow(); (*get the current front window*)
- IF aWindow # NIL THEN
- closed := DoCloseWindow(aWindow); (*close this window*)
- END
- UNTIL (¬closed) OR (aWindow = NIL); (*do all windows*)
- IF closed THEN
- SegLoad.ExitToShell (*exit if no cancellation*)
- END
- END Terminate;
-
-
- (*$S Main*)
- PROCEDURE AdjustMenus;
-
- (*Enable and disable menus based on the current state.
- The user can only select enabled menu items. We set up all the menu items
- before calling MenuSelect or MenuKey, since these are the only times that
- a menu item can be selected. Note that MenuSelect is also the only time
- the user will see menu items. This approach to deciding what enable/
- disable state a menu item has the advantage of concentrating all the decision-
- making in one routine, as opposed to being spread throughout the application.
- Other application designs may take a different approach that may or may not be
- as valid.*)
-
- VAR
- window : Windows.WindowPtr;
- menu : Menus.MenuHandle;
- offset : LONGINT;
- undo : BOOLEAN;
- cutCopyClear : BOOLEAN;
- paste : BOOLEAN;
-
- BEGIN
- window := Windows.FrontWindow();
-
- menu := Menus.GetMHandle(mFile);
- IF gNumDocuments < kMaxOpenDocuments THEN
- Menus.EnableItem(menu, iNew) (*New is enabled when we can open more documents*)
- ELSE
- Menus.DisableItem(menu, iNew)
- END;
- IF window # NIL THEN (*Close is enabled when there is a window to close*)
- Menus.EnableItem(menu, iClose)
- ELSE
- Menus.DisableItem(menu, iClose)
- END;
-
- menu := Menus.GetMHandle(mEdit);
- undo := FALSE;
- cutCopyClear := FALSE;
- paste := FALSE;
- IF IsDAWindow(window) THEN
- undo := TRUE; (*all editing is enabled for DA windows*)
- cutCopyClear := TRUE;
- paste := TRUE;
- ELSIF IsAppWindow(window) THEN
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- IF window.docTE.selStart < window.docTE.selEnd THEN
- cutCopyClear := TRUE
- END
- END;
- (*Cut, Copy, and Clear is enabled for app. windows with selections*)
- IF Scrap.GetScrap(NIL, LONG("TEXT"), offset) > 0 THEN
- paste := TRUE (*Paste is enabled for app. windows*)
- END
- END;
- IF undo THEN
- Menus.EnableItem(menu, iUndo)
- ELSE
- Menus.DisableItem(menu, iUndo)
- END;
- IF cutCopyClear THEN
- Menus.EnableItem(menu, iCut);
- Menus.EnableItem(menu, iCopy);
- Menus.EnableItem(menu, iClear);
- ELSE
- Menus.DisableItem(menu, iCut);
- Menus.DisableItem(menu, iCopy);
- Menus.DisableItem(menu, iClear);
- END;
- IF paste THEN
- Menus.EnableItem(menu, iPaste)
- ELSE
- Menus.DisableItem(menu, iPaste)
- END;
- END AdjustMenus;
-
-
- (*$S Main*)
- PROCEDURE DoMenuCommand(menuResult: LONGINT);
-
- (*This is called when an item is chosen from the menu bar (after calling
- MenuSelect or MenuKey). It does the right thing for each command.*)
-
- VAR
- menuID, menuItem : INTEGER;
- itemHit, daRefNum : INTEGER;
- daName : Types.Str255;
- ignoreResult, saveErr : Types.OSErr;
- te : TextEdit.TEHandle;
- window : Windows.WindowPtr;
- ignore : BOOLEAN;
- aHandle : Types.Handle;
- oldSize, newSize : LONGINT;
- total, contig : LONGINT;
-
- BEGIN
- window := Windows.FrontWindow();
- menuID := HiWrd(menuResult); (*use built-ins (for efficiency)...*)
- menuItem := LoWrd(menuResult); (*to get menu item number and menu number*)
- CASE menuID OF
-
- mApple:
- IF menuItem=iAbout THEN (*bring up alert for About*)
- itemHit := Dialogs.Alert(rAboutAlert, NIL);
- ELSE (*all non-About items in this menu are DAs*)
- Menus.GetItem(Menus.GetMHandle(mApple), menuItem, daName);
- daRefNum := Desk.OpenDeskAcc(daName);
- END|
-
- mFile:
- CASE menuItem OF
- iNew:
- DoNew|
- iClose:
- ignore := DoCloseWindow(window)| (*we don't care if cancelled*)
- iQuit:
- Terminate|
- ELSE (* ignore other items *)
- END|
-
- mEdit: (*call SystemEdit for DA editing & MultiFinder*)
- IF ¬Desk.SystemEdit(menuItem-1) THEN
- (*$TYPECHECK- type checks off*)
- te := window(DocumentPeek).docTE;
- (*$TYPECHECK+ type checks on*)
- CASE menuItem OF
-
- iCut: (*after cutting, export the TE scrap*)
- IF Scrap.ZeroScrap() = Types.noErr THEN
- Memory.PurgeSpace(total, contig);
- IF (te.selEnd - te.selStart) + kTESlop > contig THEN
- AlertUser(eNoSpaceCut)
- ELSE
- TextEdit.TECut(te);
- IF TextEdit.TEToScrap() # Types.noErr THEN
- AlertUser(eNoCut);
- ignoreResult := SHORT(Scrap.ZeroScrap())
- END
- END
- END|
-
- iCopy: (*after copying, export the TE scrap*)
- IF Scrap.ZeroScrap() = Types.noErr THEN
- TextEdit.TECopy(te);
- IF TextEdit.TEToScrap() # Types.noErr THEN
- AlertUser(eNoCopy);
- ignoreResult := SHORT(Scrap.ZeroScrap())
- END
- END|
-
- iPaste: (*import the TE scrap before pasting*)
- IF TextEdit.TEFromScrap() = Types.noErr THEN
- IF TextEdit.TEGetScrapLen() + (te.teLength -
- (te.selEnd - te.selStart)) > kMaxTELength THEN
- AlertUser(eExceedPaste)
- ELSE
- aHandle := Types.Handle(TextEdit.TEGetText(te));
- oldSize := Memory.GetHandleSize(aHandle);
- newSize := oldSize + TextEdit.TEGetScrapLen() + kTESlop;
- Memory.SetHandleSize(aHandle, newSize);
- saveErr := Memory.MemError();
- Memory.SetHandleSize(aHandle, oldSize);
- IF saveErr # Types.noErr THEN
- AlertUser(eNoSpacePaste)
- ELSE
- TextEdit.TEPaste(te)
- END
- END
- ELSE
- AlertUser(eNoPaste)
- END|
-
- iClear:
- TextEdit.TEDelete(te)|
- ELSE (*ignore other otems *)
-
- END;
- AdjustScrollbars(window, FALSE);
- AdjustTE(window);
- END|
- ELSE (* ignore other menus *)
- END;
- Menus.HiliteMenu(0) (*unhighlight what MenuSelect (or MenuKey) hilited*)
- END DoMenuCommand;
-
-
- (*$S Main*)
- PROCEDURE DrawWindow(window: Windows.WindowPtr);
-
- (*Draw the contents of an application window.*)
-
- BEGIN
- Quickdraw.SetPort(window);
- Quickdraw.EraseRect(window.portRect); (*as per TextEdit chapter of Inside Macintosh*)
- Controls.DrawControls(window); (*this ordering makes for a better appearance*)
- Windows.DrawGrowIcon(window);
- (*$TYPECHECK- type checks off*)
- TextEdit.TEUpdate(window.portRect, window(DocumentPeek).docTE)
- (*$TYPECHECK+ type checks on*)
- END DrawWindow;
-
-
- (*$S Main*)
- PROCEDURE GetSleep(): LONGINT;
-
- (*Calculate a sleep value for WaitNextEvent. This takes into account the things
- that DoIdle does with idle time.*)
-
- VAR
- sleep : LONGINT;
- window : Windows.WindowPtr;
-
- BEGIN
- sleep := MAX(LONGINT); (*default value for sleep*)
- IF ¬gInBackground THEN (*if we are in front...*)
- window := Windows.FrontWindow(); (*and the front window is ours...*)
- IF IsAppWindow(window) THEN
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- IF window.docTE.selStart = window.docTE.selEnd THEN (*and the selection is an insertion point...*)
- sleep := Events.GetCaretTime() (*we need to blink the insertion point*)
- END
- END
- END
- END;
- RETURN sleep
- END GetSleep;
-
-
- (*$S Main*)
- PROCEDURE CommonAction(control: Controls.ControlHandle; VAR amount: INTEGER);
-
- (*Common algorithm for setting the new value of a control. It returns the actual amount
- the value of the control changed. Note the pinning is done for the sake of returning
- the amount the control value changed.*)
-
- VAR
- value, max : INTEGER;
- BEGIN
- value := Controls.GetCtlValue(control); (*get current value*)
- max := Controls.GetCtlMax(control); (*and max value*)
- amount := value - amount;
- IF amount < 0 THEN
- amount := 0
- ELSIF amount > max THEN
- amount := max
- END;
- Controls.SetCtlValue(control, amount);
- amount := value - amount; (*calculate true change*)
- END CommonAction;
-
-
- (*$S Main*)
- (*$Calling Pascal*)
- PROCEDURE VActionProc(control: Controls.ControlHandle; part: INTEGER);
-
- (*Determines how much to change the value of the vertical scrollbar by and how
- much to scroll the TE record.*)
-
- VAR
- amount : INTEGER;
- window : Windows.WindowPtr;
- BEGIN
- IF part # 0 THEN
- window := Windows.WindowPtr(control.contrlOwner);
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- CASE part OF
- Controls.inUpButton, Controls.inDownButton:
- amount := 1| (*one line*)
- Controls.inPageUp, Controls.inPageDown:
- amount := (window.docTE.viewRect.botRight.v - window.docTE.viewRect.topLeft.v) DIV window.docTE.lineHeight| (*one page*)
- ELSE (* ignore other parts *)
- END;
- IF (part = Controls.inDownButton) OR (part = Controls.inPageDown) THEN
- amount := -amount (*reverse direction*)
- END;
- CommonAction(control, amount);
- IF amount # 0 THEN
- TextEdit.TEScroll(0, amount * window.docTE.lineHeight, window.docTE)
- END
- END
- END
- END VActionProc;
-
-
- (*$S Main*)
- PROCEDURE HActionProc(control: Controls.ControlHandle; part: INTEGER);
-
- (*Determines how much to change the value of the horizontal scrollbar by and how
- much to scroll the TE record.*)
-
- VAR
- amount : INTEGER;
- window : Windows.WindowPtr;
- BEGIN
- IF part # 0 THEN
- window := Windows.WindowPtr(control.contrlOwner);
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- CASE part OF
- Controls.inUpButton, Controls.inDownButton: (*a few pixels*)
- amount := kButtonScroll|
- Controls.inPageUp, Controls.inPageDown:
- amount := window.docTE.viewRect.botRight.h - window.docTE.viewRect.topLeft.h| (*a page*)
- ELSE (* ignore other parts *)
- END;
- IF (part = Controls.inDownButton) OR (part = Controls.inPageDown) THEN
- amount := -amount; (*reverse direction*)
- END;
- CommonAction(control, amount);
- IF amount # 0 THEN
- TextEdit.TEScroll(amount, 0, window.docTE)
- END
- END
- END
- END HActionProc;
- (*$Calling Oberon*)
-
-
- (*$S Main*)
- PROCEDURE DoIdle;
-
- (*This is called whenever we get an null event or a mouse-moved event.
- It takes care of necessary periodic actions. For this program, it calls TEIdle.*)
-
- VAR
- window : Windows.WindowPtr;
-
- BEGIN
- window := Windows.FrontWindow();
- IF IsAppWindow(window) THEN
- (*$TYPECHECK- type checks off*)
- TextEdit.TEIdle(window(DocumentPeek).docTE)
- (*$TYPECHECK+ type checks on*)
- END
- END DoIdle;
-
-
- (*$S Main*)
- PROCEDURE DoKeyDown(event: Events.EventRecord);
-
- (*This is called for any keyDown or autoKey events, except when the
- Command key is held down. It looks at the frontmost window to decide what
- to do with the key typed.*)
-
- VAR
- window : Windows.WindowPtr;
- key : CHAR;
- te : TextEdit.TEHandle;
-
- BEGIN
- window := Windows.FrontWindow();
- IF IsAppWindow(window) THEN
- (*$TYPECHECK- type checks off*)
- te := window(DocumentPeek).docTE;
- (*$TYPECHECK+ type checks on*)
- key := CHR(BAND(event.message, Events.charCodeMask));
- IF (key = kDelChar) OR (*don't count deletes*)
- (te.teLength - (te.selEnd - te.selStart)
- + 1 < kMaxTELength) THEN (*but check haven't gone past*)
- TextEdit.TEKey(key, te);
- AdjustScrollbars(window, FALSE);
- AdjustTE(window);
- ELSE
- AlertUser(eExceedChar)
- END
- END
- END DoKeyDown;
-
-
- (*$S Main*)
- PROCEDURE DoContentClick(window: Windows.WindowPtr; event: Events.EventRecord);
-
- (*Called when a mouseDown occurs in the content of a window.*)
-
- VAR
- mouse : Types.Point;
- control : Controls.ControlHandle;
- part, value : INTEGER;
- shiftDown : BOOLEAN;
- teRect : Types.Rect;
-
- BEGIN
- IF IsAppWindow(window) THEN
- Quickdraw.SetPort(window);
- mouse := event.where; (*get the click position*)
- Quickdraw.GlobalToLocal(mouse); (*convert to local coordinates*)
- (*see if we are in the viewRect. if so, we won’t check the controls*)
- GetTERect(window, teRect);
- IF Quickdraw.PtInRect(mouse, teRect) THEN
- shiftDown := BAND(event.modifiers, Events.shiftKey) # 0; (*extend if Shift is down*)
- (*$TYPECHECK- type checks off*)
- TextEdit.TEClick(mouse, shiftDown, window(DocumentPeek).docTE);
- (*$TYPECHECK+ type checks on*)
- ELSE
- part := Controls.FindControl(mouse, window, control);
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- CASE part OF
- 0:| (*do nothing for viewRect case*)
- Controls.inThumb:
- value := Controls.GetCtlValue(control);
- part := Controls.TrackControl(control, mouse, NIL);
- IF part # 0 THEN
- value := value - Controls.GetCtlValue(control);
- IF value # 0 THEN
- IF control = window.docVScroll THEN
- TextEdit.TEScroll(0, value * window.docTE.lineHeight, window.docTE)
- ELSE
- TextEdit.TEScroll(value, 0, window.docTE)
- END
- END
- END|
- ELSE (*must be page or button*)
- IF control = window.docVScroll THEN
- value := Controls.TrackControl(control, mouse, SYSTEM.ADR(VActionProc))
- ELSE
- value := Controls.TrackControl(control, mouse, SYSTEM.ADR(HActionProc))
- END
- END
- END
- END
- END
- END DoContentClick;
-
-
- (*$S Main*)
- PROCEDURE ResizeWindow(window: Windows.WindowPtr);
-
- (*Called when the window has been resized to fix up the controls and content*)
-
- BEGIN
- AdjustScrollbars(window, TRUE);
- AdjustTE(window);
- Windows.InvalRect(window.portRect);
- END ResizeWindow;
-
-
- (*$S Main*)
- PROCEDURE GetLocalUpdateRgn(window: Windows.WindowPtr; localRgn: Quickdraw.RgnHandle);
-
- (*Returns the update region in local coordinates*)
-
- BEGIN
- Quickdraw.CopyRgn(window.updateRgn, localRgn); (*save old update region*)
- Quickdraw.OffsetRgn(localRgn, window.portBits.bounds.topLeft.h, window.portBits.bounds.topLeft.v); (*convert to local coords*)
- END GetLocalUpdateRgn;
-
-
- (*$S Main*)
- PROCEDURE DoGrowWindow(window: Windows.WindowPtr; event: Events.EventRecord);
-
- (*Called when a mouseDown occurs in the grow box of an active window. In
- order to eliminate any 'flicker', we want to invalidate only what is
- necessary. Since ResizeWindow invalidates the whole portRect, we save
- the old TE viewRect, intersect it with the new TE viewRect, and
- remove the result from the update region. However, we must make sure
- that any old update region that might have been around gets put back.*)
-
- VAR
- growResult : LONGINT;
- tempRect : Types.Rect;
- tempRgn : Quickdraw.RgnHandle;
- ignoreResult : BOOLEAN;
-
- BEGIN
- Quickdraw.SetRect(tempRect, kMinDocDim, kMinDocDim,
- Quickdraw.screenBits.bounds.botRight.h,
- Quickdraw.screenBits.bounds.botRight.v); (*set up limiting values*)
- growResult := Windows.GrowWindow(window, event.where, tempRect);
- IF growResult # 0 THEN (*see if changed size*)
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- tempRect := window.docTE.viewRect; (*save old text box*)
- tempRgn := Quickdraw.NewRgn();
- GetLocalUpdateRgn(window, tempRgn); (*get localized update region*)
- Windows.SizeWindow(window, LoWrd(growResult), HiWrd(growResult), TRUE);
- ResizeWindow(window);
- ignoreResult := Quickdraw.SectRect(tempRect, window.docTE.viewRect, tempRect); (*find what stayed same*)
- Windows.ValidRect(tempRect); (*take it out of update*)
- Windows.InvalRgn(tempRgn); (*put back any prior update*)
- Quickdraw.DisposeRgn(tempRgn);
- END
- END
- END DoGrowWindow;
-
-
- (*$S Main*)
- PROCEDURE DoZoomWindow(window: Windows.WindowPtr; part: INTEGER);
-
- (*Called when a mouseClick occurs in the zoom box of an active window.
- Everything has to get re-drawn here, so we don't mind that
- ResizeWindow invalidates the whole portRect.*)
-
- BEGIN
- Quickdraw.EraseRect(window.portRect);
- Windows.ZoomWindow(window, part, window = Windows.FrontWindow());
- ResizeWindow(window);
- END DoZoomWindow;
-
-
- (*$S Main*)
- PROCEDURE DoUpdate(window: Windows.WindowPtr);
-
- (*This is called when an update event is received for a window.
- It calls DrawWindow to draw the contents of an application window,
- but only if the visRgn is non-empty; for efficiency reasons,
- not because it is required.*)
-
- BEGIN
- IF IsAppWindow(window) THEN
- Windows.BeginUpdate(window); (*this sets up the visRgn*)
- IF ¬Quickdraw.EmptyRgn(window.visRgn) THEN (*draw if updating needs to be done*)
- DrawWindow(window);
- END;
- Windows.EndUpdate(window)
- END
- END DoUpdate;
-
-
- (*$S Main*)
- PROCEDURE DoActivate(window: Windows.WindowPtr; becomingActive: BOOLEAN);
-
- (*This is called when a window is activated or deactivated.*)
-
- VAR
- tempRgn, clipRgn : Quickdraw.RgnHandle;
- growRect : Types.Rect;
-
- BEGIN
- IF IsAppWindow(window) THEN
- (*$TYPECHECK- type checks off*)
- WITH window:DocumentPeek DO
- (*$TYPECHECK+ type checks on*)
- IF becomingActive THEN
- (*since we don’t want TEActivate to draw a selection in an area where
- we’re going to erase and redraw, we’ll clip out the update region
- before calling it.*)
- tempRgn := Quickdraw.NewRgn();
- clipRgn := Quickdraw.NewRgn();
- GetLocalUpdateRgn(window, tempRgn); (*get localized update region*)
- Quickdraw.GetClip(clipRgn);
- Quickdraw.DiffRgn(clipRgn, tempRgn, tempRgn); (*subtract updateRgn from clipRgn*)
- Quickdraw.SetClip(tempRgn);
- TextEdit.TEActivate(window.docTE); (*let TE do its thing*)
- Quickdraw.SetClip(clipRgn); (*restore the full-blown clipRgn*)
- Quickdraw.DisposeRgn(tempRgn);
- Quickdraw.DisposeRgn(clipRgn);
-
- (*the controls need to be redrawn on activation:*)
- window.docVScroll.contrlVis := kControlVisible;
- window.docHScroll.contrlVis := kControlVisible;
- Windows.InvalRect(window.docVScroll.contrlRect);
- Windows.InvalRect(window.docHScroll.contrlRect);
- (*the growbox needs to be redrawn on activation:*)
- growRect := window.portRect;
- growRect.topLeft.v := growRect.botRight.v - kScrollbarAdjust; (*adjust for the scrollbars*)
- growRect.topLeft.h := growRect.botRight.h - kScrollbarAdjust;
- Windows.InvalRect(growRect);
- ELSE
- TextEdit.TEDeactivate(window.docTE);
- (*the controls should be hidden immediately on deactivation:*)
- Controls.HideControl(window.docVScroll);
- Controls.HideControl(window.docHScroll);
- (*the growbox should be changed immediately on deactivation:*)
- Windows.DrawGrowIcon(window);
- END
- END
- END
- END DoActivate;
-
-
- (*$S Main*)
- PROCEDURE GetGlobalMouse(VAR mouse: Types.Point);
-
- (*Get the global coordinates of the mouse. When you call OSEventAvail
- it will return either a pending event or a null event. In either case,
- the where field of the event record will contain the current position
- of the mouse in global coordinates and the modifiers field will reflect
- the current state of the modifiers. Another way to get the global
- coordinates is to call GetMouse and LocalToGlobal, but that requires
- being sure that thePort is set to a valid port.*)
-
- VAR
- event : Events.EventRecord;
-
- BEGIN
- IF Events.OSEventAvail(kNoEvents, event) THEN END; (*we aren't interested in any events*)
- (*$TYPECHECK- type checks off*)
- mouse := event.where; (*just the mouse position*)
- (*$TYPECHECK+ type checks on*)
- END GetGlobalMouse;
-
-
- (*$S Main*)
- PROCEDURE AdjustCursor(mouse: Types.Point; region: Quickdraw.RgnHandle);
-
- (*Change the cursor's shape, depending on its position. This also calculates the region
- where the current cursor resides (for WaitNextEvent). If the mouse is ever outside of
- that region, an event is generated, causing this routine to be called. This
- allows us to change the region to the region the mouse is currently in. If
- there is more to the event than just “the mouse moved”, we get called before the
- event is processed to make sure the cursor is the right one. In any (ahem) event,
- this is called again before we fall back into WNE.*)
-
-
- VAR
- window : Windows.WindowPtr;
- arrowRgn : Quickdraw.RgnHandle;
- iBeamRgn : Quickdraw.RgnHandle;
- iBeamRect : Types.Rect;
- aCursor : Quickdraw.CursHandle;
-
- BEGIN
- window := Windows.FrontWindow(); (*we only adjust the cursor when we are in front*)
- IF (¬gInBackground) AND (¬IsDAWindow(window)) THEN
- (*calculate regions for different cursor shapes*)
- arrowRgn := Quickdraw.NewRgn();
- iBeamRgn := Quickdraw.NewRgn();
-
- (*start with a big, big rectangular region*)
- Quickdraw.SetRectRgn(arrowRgn, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
-
- (*calculate iBeamRgn*)
- IF IsAppWindow(window) THEN
- (*$TYPECHECK- type checks off*)
- iBeamRect := window(DocumentPeek).docTE.viewRect;
- (*$TYPECHECK+ type checks on*)
- Quickdraw.SetPort(window); (*make a global version of the viewRect*)
- Quickdraw.LocalToGlobal(iBeamRect.topLeft);
- Quickdraw.LocalToGlobal(iBeamRect.botRight);
- Quickdraw.RectRgn(iBeamRgn, iBeamRect);
- Quickdraw.SetOrigin(-window.portBits.bounds.topLeft.h,
- -window.portBits.bounds.topLeft.v);
- Quickdraw.SectRgn(iBeamRgn, window^.visRgn, iBeamRgn);
- Quickdraw.SetOrigin(0, 0);
- END;
-
- (*subtract other regions from arrowRgn*)
- Quickdraw.DiffRgn(arrowRgn, iBeamRgn, arrowRgn);
-
- (*change the cursor and the region parameter*)
- IF Quickdraw.PtInRgn(mouse, iBeamRgn) THEN
- aCursor:=ToolUtils.GetCursor(ToolUtils.iBeamCursor);
- Quickdraw.SetCursor(aCursor^);
- Quickdraw.CopyRgn(iBeamRgn, region);
- ELSE
- Quickdraw.SetCursor(Quickdraw.arrow);
- Quickdraw.CopyRgn(arrowRgn, region);
- END;
-
- (*get rid of our local regions*)
- Quickdraw.DisposeRgn(arrowRgn);
- Quickdraw.DisposeRgn(iBeamRgn);
- END;
- END AdjustCursor;
-
-
- (*$S Main*)
- PROCEDURE DoEvent(event: Events.EventRecord);
-
- (*Do the right thing for an event. Determine what kind of event it is, and call
- the appropriate routines.*)
-
- VAR
- part, err : INTEGER;
- window : Windows.WindowPtr;
- key : CHAR;
- ignore : BOOLEAN;
- aPoint : Types.Point;
-
- BEGIN
- CASE event.what OF
- Events.nullEvent:
- DoIdle|
- Events.mouseDown:
- part := Windows.FindWindow(event.where, window);
- CASE part OF
- Windows.inMenuBar:
- AdjustMenus;
- DoMenuCommand(Menus.MenuSelect(event.where))|
- Windows.inSysWindow:
- Desk.SystemClick(event, window)|
- Windows.inContent:
- IF window # Windows.FrontWindow() THEN
- Windows.SelectWindow(window);
- (*DoEvent(event);*) (*use this line for "do first click"*)
- ELSE
- DoContentClick(window, event)
- END|
- Windows.inDrag:
- Windows.DragWindow(window, event.where, Quickdraw.screenBits.bounds)|
- Windows.inGrow:
- DoGrowWindow(window, event)|
- Windows.inGoAway:
- IF Windows.TrackGoAway(window, event.where) THEN
- ignore := DoCloseWindow(window); (*we don't care if cancelled*)
- END|
- Windows.inZoomIn, Windows.inZoomOut:
- IF Windows.TrackBox(window, event.where, part) THEN
- DoZoomWindow(window, part)
- END|
- ELSE (* ignore other parts *)
- END|
- Events.keyDown, Events.autoKey:
- key := CHR(BAND(event.message, Events.charCodeMask));
- IF BAND(event.modifiers, Events.cmdKey) # 0 THEN (*Command key down*)
- IF event.what = Events.keyDown THEN
- AdjustMenus; (*enable/disable/check menu items properly*)
- DoMenuCommand(Menus.MenuKey(key));
- END;
- ELSE
- DoKeyDown(event)
- END| (*call DoActivate with the window and...*)
- Events.activateEvt: (*TRUE for activate, FALSE for deactivate*)
- DoActivate(Windows.WindowPtr(event.message),
- BAND(event.modifiers, Events.activeFlag) # 0)|
- Events.updateEvt: (*call DoUpdate with the window to update*)
- DoUpdate(Windows.WindowPtr(event.message))|
- (*1.01 - It is not a bad idea to at least call DIBadMount in response
- to a diskEvt, so that the user can format a floppy.*)
- Events.diskEvt:
- IF HiWrd(event.message) # Types.noErr THEN
- Quickdraw.SetPt(aPoint, kDILeft, kDITop);
- err := DiskInit.DIBadMount(aPoint, event.message);
- END|
- kOSEvent:
- CASE BAND(SYSTEM.ROT(event.message, 8), $FF) OF (*high byte of message*)
- kMouseMovedMessage:
- DoIdle| (*mouse moved is also an idle event*)
- kSuspendResumeMessage:
- gInBackground := BAND(event.message, kResumeMask) = 0;
- DoActivate(Windows.FrontWindow(), ¬gInBackground)|
- ELSE (* ignore other events *)
- END|
- ELSE (* ignore other events *)
- END
- END DoEvent;
-
-
- (*$S Main*)
- PROCEDURE EventLoop;
-
- (*Get events forever, and handle them by calling DoEvent.
- Also call AdjustCursor each time through the loop.*)
-
- VAR
- cursorRgn : Quickdraw.RgnHandle;
- gotEvent : BOOLEAN;
- event : Events.EventRecord;
- mouse : Types.Point;
-
- BEGIN
- cursorRgn := Quickdraw.NewRgn(); (*we'll pass an empty region to WNE the first time thru*)
- LOOP
- IF gHasWaitNextEvent THEN (*put us 'asleep' forever under MultiFinder*)
- GetGlobalMouse(mouse); (*since we might go to sleep*)
- AdjustCursor(mouse, cursorRgn);
- gotEvent := Events.WaitNextEvent(Events.everyEvent, event, GetSleep(), cursorRgn);
- ELSE
- Desk.SystemTask; (*must be called if using GetNextEvent*)
- gotEvent := Events.GetNextEvent(Events.everyEvent, event);
- END;
- IF gotEvent THEN
- AdjustCursor(event.where, cursorRgn); (*make sure we have the right cursor*)
- DoEvent(event); (*Handle the event only if for us.*)
- ELSE
- DoIdle
- END;
- (*If you are using modeless dialogs that have editText items,
- you will want to call IsDialogEvent to give the caret a chance
- to blink, even if WNE/GNE returned FALSE. However, check FrontWindow
- for a non-NIL value before calling IsDialogEvent.*)
- END; (*loop forever*)
- END EventLoop;
-
-
- PROCEDURE _DataInit; EXTERNAL;
-
- (*This routine is automatically linked in by the MPW Linker. This external
- reference to it is done so that we can unload its segment, %A5Init.*)
-
-
- (*$S Main*)
- (*$MAIN*)
- BEGIN
- SegLoad.UnloadSeg(SYSTEM.ADR(_DataInit)); (*note that _DataInit must not be in Main!*)
-
- (*1.01 - call to ForceEnvirons removed*)
- (*If you have stack requirements that differ from the default,
- then you could use SetApplLimit to increase StackSpace at
- this point, before calling MaxApplZone.*)
-
- Memory.MaxApplZone; (*expand the heap so code segments load at the top*)
-
- Initialize; (*initialize the program*)
- SegLoad.UnloadSeg(SYSTEM.ADR(Initialize)); (*note that Initialize must not be in Main!*)
-
- EventLoop; (*call the main event loop*)
- END TESample.
-